// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/directory.h>
#include <fs/synchronous-vfs.h>
#include <fs/pseudo-dir.h>
#include <fs/service.h>

#include <unittest/unittest.h>

#include <utility>

namespace {

bool test_service() {
  BEGIN_TEST;

  // set up a service which can only be bound once (to make it easy to
  // simulate an error to test error reporting behavior from the connector)
  zx::channel bound_channel;
  auto svc = fbl::AdoptRef<fs::Service>(new fs::Service([&bound_channel](zx::channel channel) {
    if (bound_channel)
      return ZX_ERR_IO;
    bound_channel = std::move(channel);
    return ZX_OK;
  }));

  // open
  fbl::RefPtr<fs::Vnode> redirect;
  EXPECT_EQ(ZX_OK, svc->ValidateFlags(ZX_FS_RIGHT_READABLE));
  EXPECT_EQ(ZX_OK, svc->Open(ZX_FS_RIGHT_READABLE, &redirect));
  EXPECT_NULL(redirect);

  // get attr
  vnattr_t attr;
  EXPECT_EQ(ZX_OK, svc->Getattr(&attr));
  EXPECT_EQ(V_TYPE_FILE, attr.mode);
  EXPECT_EQ(1, attr.nlink);

  // make some channels we can use for testing
  zx::channel c1, c2;
  EXPECT_EQ(ZX_OK, zx::channel::create(0u, &c1, &c2));
  zx_handle_t hc1 = c1.get();

  // serve, the connector will return success the first time
  fs::SynchronousVfs vfs;
  EXPECT_EQ(ZX_OK, svc->Serve(&vfs, std::move(c1), ZX_FS_RIGHT_READABLE));
  EXPECT_EQ(hc1, bound_channel.get());

  // the connector will return failure because bound_channel is still valid
  // we test that the error is propagated back up through Serve
  EXPECT_EQ(ZX_ERR_IO, svc->Serve(&vfs, std::move(c2), ZX_FS_RIGHT_READABLE));
  EXPECT_EQ(hc1, bound_channel.get());

  END_TEST;
}

bool TestServeDirectory() {
  BEGIN_TEST;

  zx::channel client, server;
  EXPECT_EQ(ZX_OK, zx::channel::create(0u, &client, &server));

  // open client
  zx::channel c1, c2;
  EXPECT_EQ(ZX_OK, zx::channel::create(0u, &c1, &c2));
  EXPECT_EQ(ZX_OK, fdio_service_connect_at(client.get(), "abc", c2.release()));

  // close client
  // We test the semantic that a pending open is processed even if the client
  // has been closed.
  client.reset();

  // serve
  async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
  fs::SynchronousVfs vfs(loop.dispatcher());

  auto directory = fbl::AdoptRef<fs::PseudoDir>(new fs::PseudoDir());
  auto vnode = fbl::AdoptRef<fs::Service>(new fs::Service([&loop](zx::channel channel) {
    loop.Shutdown();
    return ZX_OK;
  }));
  directory->AddEntry("abc", vnode);

  EXPECT_EQ(ZX_OK, vfs.ServeDirectory(directory, std::move(server)));
  EXPECT_EQ(ZX_ERR_BAD_STATE, loop.RunUntilIdle());

  END_TEST;
}

}  // namespace

BEGIN_TEST_CASE(service_tests)
RUN_TEST(test_service)
RUN_TEST(TestServeDirectory)
END_TEST_CASE(service_tests)
